#!/bin/bash
#######################################################
# Author: liangliangSu
# Created Time: 2023-09-20 15:30
# Email: sll917@outlook.com
# Version: V1.0
# File Name: bash_备忘清单.sh
#######################################################
echo '(1)------------------完美分割线---------------------'
#!/bin/bash
VAR="world"
echo "Hello $VAR!" # => Hello world!

# 变量
NAME="John"
echo ${NAME}    # => John (变量)
echo $NAME      # => John (变量)
echo "$NAME"    # => John (变量)
echo '$NAME'    # => $NAME (字符串原样输出)
echo "${NAME}!" # => John! (变量)
# NAME = "John"   # => Error (注意不能有空格)

# 注释
# 这是一个内联 Bash 注释。
: '
这是一个
非常整洁的
bash 注释
'
# 多行注释使用 :' 打开和 ' 关闭

: '
参数
$1 … $9	参数 1 ... 9
$0	脚本本身的名称
$1	第一个参数
${10}	位置参数 10
$#	参数数量
$$	shell 的进程 id
$*	所有参数
$@	所有参数，从第一个开始
$-	当前选项
$_	上一个命令的最后一个参数
见：特殊参数
'

echo '(2)------------------完美分割线---------------------'
# 函数
get_name() {
    echo "John"
}
echo "You are $(get_name)"

# 条件句
if [[ -z "$string" ]]; then
    echo "String is empty"
elif [[ -n "$string" ]]; then
    echo "String is not empty"
fi

# 大括号扩展
echo {A,B}.js

# Shell 执行
# => I'm in /当前/的/路径
echo "I'm in ${PWD}"
# Same as:
echo "I'm in `pwd`"

# Bash 参数扩展
: '
# 语法
${FOO%suffix}	删除后缀
${FOO#prefix}	删除前缀
${FOO%%suffix}	去掉长后缀
${FOO##prefix}	删除长前缀
${FOO/from/to}	替换第一个匹配项
${FOO//from/to}	全部替换
${FOO/%from/to}	替换后缀
${FOO/#from/to}	替换前缀
子字符串
${FOO:0:3}	子串 (位置，长度)
${FOO:(-3):3}	从右边开始的子串
Length
${#FOO}	$FOO 的长度
默认值
${FOO:-val}	$FOO，如果未设置，则为 val
${FOO:=val}	如果未设置，则将 $FOO 设置为 val
${FOO:+val}	val 如果设置了$FOO
${FOO:?message}	如果 $FOO 未设置，则显示消息并退出
'
echo '(3)------------------完美分割线---------------------'
# 替代 Substitution
echo ${food:-Cake}  #=> $food or "Cake"
STR="/path/to/foo.cpp"
echo ${STR%.cpp}    # /path/to/foo
echo ${STR%.cpp}.o  # /path/to/foo.o
echo ${STR%/*}      # /path/to
echo ${STR##*.}     # cpp (extension)
echo ${STR##*/}     # foo.cpp (basepath)
echo ${STR#*/}      # path/to/foo.cpp
echo ${STR##*/}     # foo.cpp
echo ${STR/foo/bar} # /path/to/bar.cpp
# 切片 Slicing
name="John"
echo ${name}           # => John
echo ${name:0:2}       # => Jo
echo ${name::2}        # => Jo
echo ${name::-1}       # => Joh
echo ${name:(-1)}      # => n
echo ${name:(-2)}      # => hn
echo ${name:(-2):2}    # => hn
length=2
echo ${name:0:length}  # => Jo

echo '(4)------------------完美分割线---------------------'
# 基本路径和目录路径
SRC="/path/to/foo.cpp"
BASEPATH=${SRC##*/}
echo $BASEPATH  # => "foo.cpp"

DIRPATH=${SRC%$BASEPATH}
echo $DIRPATH   # => "/path/to/"
# Transform
STR="HELLO WORLD!"
echo ${STR,}      # => hELLO WORLD!
echo ${STR,,}     # => hello world!

STR="hello world!"
echo ${STR^}      # => Hello world!
echo ${STR^^}     # => HELLO WORLD!

ARR=(hello World)
echo "${ARR[@],}" # => hello world
echo "${ARR[@]^}" # => Hello World

echo '(5)------------------完美分割线---------------------'
# Bash 数组
# 定义数组
Fruits=('Apple' 'Banana' 'Orange')
 
Fruits[0]="Apple"
Fruits[1]="Banana"
Fruits[2]="Orange"

ARRAY1=(foo{1..2}) # => foo1 foo2
ARRAY2=({A..D})    # => A B C D
 
# 合并 => foo1 foo2 A B C D
ARRAY3=(${ARRAY1[@]} ${ARRAY2[@]})
 
# 声明构造
declare -a Numbers=(1 2 3)
Numbers+=(4 5) # 附加 => 1 2 3 4 5
: '
索引
${Fruits[0]}	第一个元素
${Fruits[-1]}	最后一个元素
${Fruits[*]}	所有元素
${Fruits[@]}	所有元素
${#Fruits[@]}	总数
${#Fruits}	第一节长度
${#Fruits[3]}	第n个长度
${Fruits[@]:3:2}	范围
${!Fruits[@]}	所有 Key
'

echo '(6)------------------完美分割线---------------------'
# 迭代 Iteration
Fruits=('Apple' 'Banana' 'Orange')
for e in "${Fruits[@]}"; do
    echo $e
done
# With index
for i in "${!Fruits[@]}"; do
  printf "%s\t%s\n" "$i" "${Fruits[$i]}"
done

# 操作
# Fruits=("${Fruits[@]}" "Watermelon")         # 添加
# Fruits+=('Watermelon')                       # 也是添加
# Fruits=( ${Fruits[@]/Ap*/} )                 # 通过正则表达式匹配删除
# unset Fruits[2]                              # 删除一项
# Fruits=("${Fruits[@]}")                      # 复制
# Fruits=("${Fruits[@]}" "${Veggies[@]}")      # 连接
# lines=(`cat "logfile"`)                      # 从文件中读取

# 数组作为参数
function extract()
{
  local -n myarray=$1
  local idx=$2
  echo "${myarray[$idx]}"
}
Fruits=('Apple' 'Banana' 'Orange')
extract Fruits 2     # => Orangle
extract Fruits 1     # => Banana

# Bash 字典
# 定义
echo '(7)------------------完美分割线---------------------'
declare -A sounds
sounds[dog]="bark"
sounds[cow]="moo"
sounds[bird]="tweet"
sounds[wolf]="howl"
# 使用字典
echo ${sounds[dog]} # Dog's sound
echo ${sounds[@]}   # All values
echo ${!sounds[@]}  # All keys
echo ${#sounds[@]}  # Number of elements
unset sounds[dog]   # Delete dog

# 迭代
echo '(8)------------------完美分割线---------------------'
for val in "${sounds[@]}"; do
    echo $val
done
echo '(9)------------------完美分割线---------------------'
for key in "${!sounds[@]}"; do
    echo $key
done

# Bash 条件句
: '
整数条件
[[ NUM -eq NUM ]]	等于 Equal
[[ NUM -ne NUM ]]	不等于 Not equal
[[ NUM -lt NUM ]]	小于 Less than
[[ NUM -le NUM ]]	小于等于 Less than or equal
[[ NUM -gt NUM ]]	大于 Greater than
[[ NUM -ge NUM ]]	大于等于 Greater than or equal
(( NUM < NUM ))	小于
(( NUM <= NUM ))	小于或等于
(( NUM > NUM ))	比...更大
(( NUM >= NUM ))	大于等于

字符串条件
[[ -z STR ]]	空字符串
[[ -n STR ]]	非空字符串
[[ STR == STR ]]	相等
[[ STR = STR ]]	相等(同上)
[[ STR < STR ]]	小于 (ASCII)
[[ STR > STR ]]	大于 (ASCII)
[[ STR != STR ]]	不相等
[[ STR =~ STR ]]	正则表达式
'
# 例子
# 字符串
if [[ -z "$string" ]]; then
    echo "String is empty"
elif [[ -n "$string" ]]; then
    echo "String is not empty"
else
    echo "This never happens"
fi
# 组合
# if [[ X && Y ]]; then
#     ...
# fi
# # 相等
# if [[ "$A" == "$B" ]]; then
#     ...
# fi

# 正则表达式
if [[ '1. abc' =~ ([a-z]+) ]]; then
    echo ${BASH_REMATCH[1]}
fi
# 小于
a=2
b=4
if (( $a < $b )); then
   echo "$a is smaller than $b"
fi
# 存在
if [[ -e "file.txt" ]]; then
    echo "file exists"
fi

: '
文件条件
[[ -e FILE ]]	存在
[[ -d FILE ]]	目录
[[ -f FILE ]]	文件
[[ -h FILE ]]	符号链接
[[ -s FILE ]]	大小 > 0 字节
[[ -r FILE ]]	可读
[[ -w FILE ]]	可写
[[ -x FILE ]]	可执行文件
[[ f1 -nt f2 ]]	f1 比 f2 新
[[ f1 -ot f2 ]]	f2 比 f1 新
[[ f1 -ef f2 ]]	相同的文件
更多条件
[[ -o noclobber ]]	如果启用 OPTION
[[ ! EXPR ]]	不是 Not
[[ X && Y ]]	和 And
[[ X || Y ]]	或者 Or
逻辑和，或
if [ "$1" = 'y' -a $2 -gt 0 ]; then
    echo "yes"
fi
if [ "$1" = 'n' -o $2 -lt 0 ]; then
    echo "no"
fi
'

# Bash 循环
# 基本 for 循环
echo '(10)------------------完美分割线---------------------'
for i in /home/sll/gitee/shell/*.sh; do
    echo $i
done
# 类似 C 的 for 循环
for ((i = 0 ; i < 100 ; i++)); do
    echo $i
done
# 范围
for i in {1..5}; do
    echo "Welcome $i"
done
# 步长
for i in {5..50..5}; do
    echo "Welcome $i"
done
echo '(11)------------------完美分割线---------------------'
# 自动递增
i=1
while [[ $i -lt 4 ]]; do
    echo "Number: $i"
    ((i++))
done
# 自动递减
i=3
while [[ $i -gt 0 ]]; do
    echo "Number: $i"
    ((i--))
done
echo '(12)------------------完美分割线---------------------'
# Continue
for number in $(seq 1 5); do
    if [[ $number == 3 ]]; then
        continue;
    fi
    echo "$number"
done
# Break
echo '------------------完美分割线---------------------'
for number in $(seq 1 5); do
    if [[ $number == 3 ]]; then
        # Skip entire rest of loop.
        break;
    fi
    # This will only print 1
    echo "$number"
done
# Until
echo '(13)------------------完美分割线---------------------'
count=0
until [ $count -gt 10 ]; do
    echo "$count"
    ((count++))
done
# 死循环
# while true; do
#     # here is some code.
# done
# 死循环(简写)
# while :; do
#     # here is some code.
# done
echo '(14)------------------完美分割线---------------------'
# 读取文件的每一行
cat push_code.sh | while read line; do
    echo $line
done

echo '(15)------------------完美分割线---------------------'
# Bash 函数
# 定义函数
# myfunc() {
#   echo "hello $1"
# }
# 同上(替代语法)
# function myfunc() {
#   echo "hello $1"
# }
# myfunc "John"
# 返回值
myfunc() {
    local myresult='some value'
    echo $myresult
}
result="$(myfunc)"
# 抛出错误
myfunc() {
    return 1
}
if myfunc; then
    echo "success"
else
    echo "failure"
fi

echo '(16)------------------完美分割线---------------------'
# Bash 选项
# 选项
# # 避免覆盖文件
# # (echo "hi" > foo)
# set -o noclobber
 
# # 用于出错时退出
# # 避免级联错误
# set -o errexit
 
# # 揭示隐藏的失败
# set -o pipefail
 
# # 公开未设置的变量
# set -o nounset

# 全局选项
# # 不匹配的 glob 被删除
# # ('*.foo' => '')
# shopt -s nullglob
 
# # 不匹配的 glob 抛出错误
# shopt -s failglob

# # 不区分大小写的球体
# shopt -s nocaseglob
 
# # 通配符匹配点文件
# # ("*.sh" => ".foo.sh")
# shopt -s dotglob
 
# # 允许 ** 进行递归匹配
# # ('lib/**/*.rb' => 'lib/a/b/c.rb')
# shopt -s globstar

# Bash 历史
# 命令
: '
history	显示历史
sudo !!	使用 sudo 运行上一个命令
shopt -s histverify	不要立即执行扩展结果
表达式
!$	展开最新命令的最后一个参数
!*	展开最新命令的所有参数
!-n	展开第 n 个最近的命令
!n	展开历史中的第 n 个命令
!<command>	展开最近调用的命令 <command>
操作
!!	再次执行最后一条命令
!!:s/<FROM>/<TO>/	在最近的命令中将第一次出现的 <FROM> 替换为 <TO>
!!:gs/<FROM>/<TO>/	在最近的命令中将所有出现的 <FROM> 替换为 <TO>
!$:t	仅从最近命令的最后一个参数扩展基本名称
!$:h	仅从最近命令的最后一个参数展开目录
!! 和 !$ 可以替换为任何有效的扩展。

切片 Slices
!!:n	仅扩展最近命令中的第 n 个标记(命令为 0；第一个参数为 1)
!^	从最近的命令展开第一个参数
!$	从最近的命令中展开最后一个标记
!!:n-m	从最近的命令扩展令牌范围
!!:n-$	从最近的命令中将第 n 个标记展开到最后
!! 可以替换为任何有效的扩展，即 !cat、!-2、!42 等。
'

# 杂项
# 数值计算
# $((a + 200))      # Add 200 to $a
# $(($RANDOM%200))  # Random number 0..199
# 子 shell
# (cd somedir; echo "I'm now in $PWD")
# pwd # still in first directory
# 检查命令
# command -V cd
# #=> "cd is a function/alias/whatever"
# 重定向
# python hello.py > output.txt   # 标准输出到(文件)
# python hello.py >> output.txt  # 标准输出到(文件)，追加
# python hello.py 2> error.log   # 标准错误到(文件)
# python hello.py 2>&1           # 标准错误到标准输出
# python hello.py 2>/dev/null    # 标准错误到(空null)
# python hello.py &>/dev/null    # 标准输出和标准错误到(空null)
# python hello.py < foo.txt      # 将 foo.txt 提供给 python 的标准输入
# 来源相对
# source "${0%/*}/../share/foo.sh"
# 脚本目录
# DIR="${0%/*}"

# Case/switch
# case "$1" in
#     start | up)
#     vagrant up
#     ;;
#     *)
#     echo "Usage: $0 {start|stop|ssh}"
#     ;;
# esac
# 陷阱错误
# trap 'echo Error at about $LINENO' ERR
# 或者
# 
# traperr() {
#     echo "ERROR: ${BASH_SOURCE[1]} at about ${BASH_LINENO[0]}"
# }
# set -o errtrace
# trap traperr ERR

# printf
printf "Hello %s, I'm %s\n" Sven Olga
#=> "Hello Sven, I'm Olga

printf "1 + 1 = %d\n" 2
#=> "1 + 1 = 2"

printf "Print a float: %f\n" 2
#=> "Print a float: 2.000000"
# 获取选项
# read -p "pleas input str:" Key
# while [[ "$Key" =~ ^- && ! "$Key" == "--" ]]; do case $Key in
#     -V | --version )
#     echo $version
#     exit
#     ;;
#     -s | --string )
#     shift; string=$Key
#     ;;
#     -f | --flag )
#     flag=Key
#     ;;
# esac; shift; done
# if [[ "$Key" == '--' ]]; then shift; fi
# 
# # 检查命令的结果
# if ping -c 1 google.com; then
#     echo "看来您的互联网连接正常"
# else
#     echo "看来您的互联网连接异常"
# fi
# grep 检查
# if grep -q 'foo' ~/.bash_history; then
#     echo "您过去似乎输入过“foo”"
# fi
# 写入文件
# cat	用于显示文本文件内容，全部输出
# EOF	end of file，表示文本结束符
# cat > output.txt <<EOF
# this is test eof
# this is test eof2
# EOF
# 输出 cat output.txt
# this is test eof
# this is test eof2
# 追加内容
# cat >>2.txt <<EOF
# 456
# 789
# EOF

# $ 等特殊字符时，须利用转义字符 \
# cat > file <<EOF
# export ORACLE_SID=yqpt
# export PATH=\$PATH:\$ORACLE_HOME/bin
# EOF
# 取消变量替换，EOF 被加上双引号或者单引号，即可取消变量的替换
# 
# cat << "EOF" > output.sh
# echo "This is output"
# echo $1
# EOF
# 所有TAB键将全部忽略[不能是空格]
# cat << -EOF
#         echo "This is output"
# EOF

# 特殊变量
# $?	最后一个任务的退出状态
# $!	最后一个后台任务的 PID
# $$	shell PID
# $0	shell 脚本的文件名
 
: '
#反斜杠转义
!
"
#
&
(
)
,
;
<
>
[
|
\
]
^
{
}
`
$
*
?
'
# 使用 \ 转义这些特殊字符
 
# Heredoc
# cat <<END
# hello world
# END
# 转到上一个目录
# pwd # /home/user/foo
# cd bar/
# pwd # /home/user/foo/bar
# cd -
# pwd # /home/user/foo
# 读取输入
# echo -n "Proceed? [y/n]: "
# read ans
# echo $ans
# read -n 1 ans    # 只有一个字符
# 条件执行
# git commit && git push
# git commit || echo "Commit failed"
# 严格模式
# set -euo pipefail
# IFS=$'\n\t'
# 参见：非官方 bash 严格模式
# 
# 可选参数
# args=("$@")
# args+=(foo)
# args+=(bar)
# echo "${args[@]}"
# 将参数放入数组中，然后追加
# 
# 调试模式
# 启用调试模式，会把脚本中的每条命令的执行情况打印出来。它可以在整个会话或脚本上运行，也可以在脚本内以编程方式启用。
# 
# 以调试模式运行脚本(整个脚本都会打印调试信息)：
# 
# $ bash -x myscript.sh
# 在bash脚本中打开调试(针对部分内容打印调试信息)。
# 
# #!/bin/bash
# set -x   # Enable debugging
# # some code here
# set +x   # Disable debugging output.

# Bash 颜色
# 颜色着色语法示例
# 您可以通过为其输出着色来使您的 BASH 脚本更漂亮，使用以下模板编写彩色文本：
 
# echo -e "\e[COLORm文字变色了\e[0m"
# 示例
# $ echo -e "\e[31m命令行中显示红色文字\e[0m"
# 命令行中显示红色文字
# 
# $ echo -e "\e[42m绿色背景\e[0m"
# 绿色背景
# -e	启用反斜杠转义的解释
# \e[ 或 \x1b[ 或 \033[	开始颜色修改
# COLORm	颜色代码 + m 在末尾
# \e[0m 或 \x1b[0m 或 \033[0m	结束颜色修改
# 0x1b 字面上称为 ESC
# 
# 示例
# $ echo -e "\e[3m下划线文本\e[0m"
# 下划线文本
# $ echo -e "\e[1;33;4;44m粗体下划线蓝色背景黄色文字的文本\e[0m"
# 粗体下划线蓝色背景黄色文字的文本
# \e[         # 启用转义
# 1;33;4;44   # 参数 (1;33;4;44)
# m           # 设置图形模式
# 样式
# 0	普通字符(复位或正常)	关闭所有属性
# 1	粗体字
# 2	弱化(降低强度)	未广泛支持
# 3	斜体	未广泛支持有时为反相显示
# 4	下划线
# 5	缓慢闪烁
# 6	快速闪烁
# 7	反显
# 8	隐藏	未广泛支持。
# 9	划除
# ANSI — 颜色转义码
# 颜色	前景色	背景色	示例
# Black 黑色	30	40	``
# Red 红色	31	41	``
# Green 绿色	32	42	``
# Yellow 黄色	33	43	``
# Blue 蓝色	34	44	``
# Purple 紫色	35	45	``
# Cyan 青色	36	46	``
# White 白色	37	47	``
# ANSI — 颜色转义码(亮色)
# 颜色	前景色	背景色	示例
# Bright Black(Gray) 灰色	90	100	``
# Bright Red 亮红色	91	101	``
# Bright Green 亮绿色	92	102	``
# Bright Yellow 亮黄色	93	103	``
# Bright Blue 亮蓝色	94	104	``
# Bright Magenta 亮紫色	95	105	``
# Bright Cyan 亮青色	96	106	``
# Bright White 浅灰	97	107	``
# 可用功能
# n A	光标上移	光标向指定的方向移动 n(默认1)格
# n B	光标下移	光标向指定的方向移动 n(默认1)格
# n C	光标前移	光标向指定的方向移动 n(默认1)格
# n D	光标后移	光标向指定的方向移动 n(默认1)格
# n E	光标移到下一行	光标移动到下面第 n(默认1)行的开头(非ANSI.SYS)
# n F	光标移到上一行	光标移动到上面第 n(默认1)行的开头(非ANSI.SYS)
# n G	光标水平绝对	光标移动到第 n(默认1)列(非ANSI.SYS)
# n;m H	光标位置	光标移动到第 n行、第 m 列。值从1开始，且默认为1(左上角)
# n J	ED – 擦除显示	清除屏幕的部分区域
# n K	EL – 擦除行	清除行内的部分区域
# n S	SU – 向上滚动	整页向上滚动 n(默认1)行。新行添加到底部 (非ANSI.SYS)
# n T	SD – 向下滚动	整页向下滚动 n(默认1)行。新行添加到顶部 (非ANSI.SYS)
# n;mf	HVP – 水平垂直位置	同CUP
# n m	SGR – 选择图形再现	设置SGR参数，包括文字颜色
# 5i	打开辅助端口	启用辅助串行端口，通常用于本地串行打印机
# 4i	关闭辅助端口	禁用辅助串行端口，通常用于本地串行打印机
# 6n	DSR – 设备状态报告	以 ESC[n;mR (就像在键盘上输入)向应用程序报告光标位置(CPR)，其中 n是行， m 是列
# s	SCP – 保存光标位置	保存光标的当前位置
# u	RCP – 恢复光标位置	恢复保存的光标位置
